package org.elec.mac.gizwits;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.elec.mac.constants.Constants;
import org.elec.mac.entity.GizToken;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.WebSocket;

/**
 * <a href="http://docs.gizwits.com/zh-cn/Cloud/openapi_apps.html">机智云openApi接口</a>
 * <a href=http://docs.gizwits.com/zh-cn/Cloud/WebsocketAPI.html>机智云webSocket接口</a>
 * <p>
 * 具体流程：通过 一、openApi登录接口获取 uid 和 token -> 二、绑定设备接口获取设备did -> 三、打开webSocket连接 ->
 * 四、通过 uid 和 token 登录webSocket -> 五、通过webSocket订阅，订阅后才能进行通讯。
 * <p>
 * 常用接口：
 * 1、openApi 登录接口{@link #openApiLogin(String, String, String)}
 * 2、openApi 绑定设备接口{@link #bind(String, String, String, String, String)}
 * 3、webSocket 登录接口{@link #wsLogin(WebSocket, String, GizToken)}
 * 4、WebSocket 订阅接口{@link #subscribe(String...),#subscribe(List)}
 * 5、webSocket 获取数据点接口{@link #readStandardDataPoint(String, String...)}
 * 6、webSocket 修改数据点接口{@link #writeStandardDataPoint(String, JSONObject)}
 * <p>
 * 其他接口：
 * openApi 解绑设备接口{@link #unBind(String, String, String...)}
 */
@Slf4j
@Component
public class GizApi {

    /**
     * openApi登录接口，返回{@link GizToken}，获取user_token和uid
     * 注：user_token 有效期为7天，重新获取token时，新旧token都能使用
     *
     * @param appId    应用唯一id
     * @param username 用户名
     * @param password 用户密码
     * @return {@link GizToken}
     * @throws IOException
     */
    public GizToken openApiLogin(String appId, String username, String password) throws IOException {

        String url = "http://api.gizwits.com/app/login";
        Map<String, String> headers = new HashMap<>();
        headers.put("X-Gizwits-Application-Id", appId);
        JSONObject params = new JSONObject();
        params.put("username", username);
        params.put("password", password);
        params.put("lang", "en");

        Response response = POST(url, params, headers);
        String result = response.body().string();
        JSONObject jsonObject = new JSONObject(result);

        if (!response.isSuccessful()) {

            String errorMsg = null;
            try {
                errorMsg = jsonObject.getString("error_message");
            } catch (JSONException e) {
                e.printStackTrace();
            }
            log.error("openApi登录失败 : error_message : " + errorMsg);
            return null;
        }

        if (StringUtils.isBlank(result)) {
            log.error("openApi登录，返回结果为空");
            return null;
        }

        GizToken gizToken = null;

        try {
            String token = jsonObject.getString("token");
            String uid = jsonObject.getString("uid");
            long expire_at = jsonObject.getLong("expire_at");
            gizToken = new GizToken(token, uid, expire_at);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return gizToken;
    }

    /**
     * 绑定设备
     *
     * @param appid         应用唯一id
     * @param userToken     用户token，通过openApi创建用户接口或者登录接口获取，一周期限。
     * @param productSecret 产品秘钥
     * @param productKey    产品key
     * @param mac           产品mac码
     * @return 返回设备的did
     */
    public String bind(String appid, String userToken, String productSecret, String productKey, String mac) throws IOException {

        String url = "http://api.gizwits.com/app/bind_mac";

        String time = String.valueOf(System.currentTimeMillis() / 1000);

        log.info("time : " + time);
        Map<String, String> headers = new HashMap<>();
        headers.put("X-Gizwits-Application-Id", appid);
        headers.put("X-Gizwits-User-token", userToken);
        headers.put("X-Gizwits-Timestamp", time);

        String sign = DigestUtils.md5Hex(productSecret + time);

        headers.put("X-Gizwits-Signature", sign);

        JSONObject param = new JSONObject();
        param.put("product_key", productKey);
        param.put("mac", mac);

        Response response = POST(url, param, headers);

        String result = response.body().string();
        JSONObject jsonObject = new JSONObject(result);

        if (!response.isSuccessful()) {

            String errorMsg = null;
            try {
                errorMsg = jsonObject.getString("error_message");
            } catch (JSONException e) {
                e.printStackTrace();
            }
            log.error("绑定设备请求失败 : error_message : " + errorMsg);
            return null;
        }

        if (StringUtils.isBlank(result)) {
            log.error("绑定设备，返回结果为空");
            return null;
        }

        String did = null;

        try {
            did = jsonObject.getString("did");
        } catch (JSONException e) {
            log.error("did 为空");
        }

        return did;
    }


    /**
     * webSocket 登录，结果在结果在{@link GizWebSocketListener#onMessage(WebSocket, String)}中返回
     * 心跳默认设置为60s，已经绑定的did会自动订阅
     *
     * @param appId    应用唯一id
     * @param gizToken 机智云token
     * @return 是否成功放入队列中
     */
    public boolean wsLogin(WebSocket webSocket, String appId, GizToken gizToken) {

        if (gizToken != null) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("cmd", "login_req");
            JSONObject dataObject = new JSONObject();
            dataObject.put("appid", appId);
            dataObject.put("uid", gizToken.getUid());
            dataObject.put("token", gizToken.getToken());
            dataObject.put("p0_type", "attrs_v4");
            dataObject.put("heartbeat_interval", 60);
            dataObject.put("auto_subscribe", true);
            jsonObject.put("data", dataObject);
            return webSocket.send(jsonObject.toString());
        }

        return false;
    }

    /**
     * WebSocket 订阅设备，结果在{@link GizWebSocketListener#onMessage(WebSocket, String)}中返回
     *
     * @param dids 设备did列表
     * @return 是否成功放入队列中
     */
    public boolean subscribe(List<String> dids) {
        WebSocket webSocket = Constants.webSocket;

        if (webSocket == null) {
            log.error("Giz WebSocket为被初始化");
            return false;
        }

        JSONObject params = new JSONObject();
        JSONArray data = new JSONArray();
        params.put("cmd", "subscribe_req");

        for (String did : dids) {
            data.put(new JSONObject().put("did", did));
        }

        params.put("data", data);

        return webSocket.send(params.toString());
    }

    /**
     * WebSocket 订阅设备,结果在websocket中返回
     *
     * @param dids 设备did可变参数
     * @return 是否成功放入队列中
     */
    public boolean subscribe(String... dids) {
        return subscribe(Arrays.asList(dids));
    }

    /**
     * 读取该 did 设备的标准数据点（变长、定长）
     *
     * @param did        设备id
     * @param pointNames 数据点可变参数，没有则默认全部
     * @return 是否成功放入队列中
     */
    public boolean readStandardDataPoint(String did, String... pointNames) {
        WebSocket webSocket = Constants.webSocket;

        if (webSocket == null) {
            log.error("Giz WebSocket为被初始化");
            return false;
        }
        JSONObject params = new JSONObject();
        JSONObject data = new JSONObject();

        if (pointNames != null && pointNames.length > 0) {
            data.put("names", new JSONArray(Arrays.asList(pointNames)));
        }

        data.put("did", did);
        params.put("cmd", "c2s_read");
        params.put("data", data);

        return webSocket.send(params.toString());
    }

    /**
     * 修改该 did 设备的数据点
     *
     * @param did   目标设备id
     * @param attrs 数据点键值对
     * @return 是否成功放入队列中
     */
    public boolean writeStandardDataPoint(String did, JSONObject attrs) {
        WebSocket webSocket = Constants.webSocket;

        if (webSocket == null) {
            log.error("Giz WebSocket为被初始化");
            return false;
        }

        JSONObject params = new JSONObject();
        JSONObject data = new JSONObject();
        JSONObject attrsObj = new JSONObject();
        params.put("cmd", "c2s_write");
        data.put("did", did);

        if (attrs == null) {
            log.error("数据点不能为空");
            return false;
        }

        attrsObj.put("attrsObj", attrs);
        data.put("attrs", attrs);

        params.put("data", data);

        return webSocket.send(params.toString());
    }

    /**
     * 解除绑定
     *
     * @param appid     应用唯一id
     * @param userToken 用户token
     * @param dids      需要解绑的设备id可变参数
     * @return 若全部解绑成功则返回true
     * @throws IOException
     */
    public boolean unBind(String appid, String userToken, String... dids) throws IOException {

        String url = "https://api.gizwits.com/app/bindings";
        Map<String, String> headers = new HashMap<>();
        headers.put("X-Gizwits-Application-Id", appid);
        headers.put("X-Gizwits-User-token", userToken);

        if (dids == null || dids.length < 1) {
            log.error("unBind接口 dids列表不能为空");
            return false;
        }

        JSONObject params = new JSONObject();
        JSONArray devices = new JSONArray();

        JSONObject did = new JSONObject();

        for (String s : dids) {
            did.put("did", s);
            devices.put(did);
        }

        params.put("devices", devices);

        Response response = DELETE(url, params, headers);
        String result = response.body().string();
        JSONObject jsonObject = new JSONObject(result);

        if (!response.isSuccessful()) {

            String errorMsg = null;
            try {
                errorMsg = jsonObject.getString("error_message");
            } catch (JSONException e) {
                e.printStackTrace();
            }
            log.error("openApi解除绑定失败 : error_message : " + errorMsg);
            return false;
        }

        if (StringUtils.isBlank(result)) {
            log.error("openApi解除绑定，返回结果为空");
            return false;
        }

        JSONArray failureArray = jsonObject.getJSONArray("failed");
        JSONArray successArray = jsonObject.getJSONArray("success");

        if (failureArray.length() > 0) {
            failureArray.forEach(s -> log.error("解绑失败 ：" + s));
            return false;
        }

        successArray.forEach(s -> log.info("解绑成功 ：" + s));

        return false;
    }

    private OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .writeTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .connectTimeout(30, TimeUnit.SECONDS)
            .build();


    public Response POST(String Url, JSONObject params, Map<String, String> headers) throws IOException {
        RequestBody body = FormBody.create(MediaType.parse("application/json; charset=utf-8"), params.toString());

        Request.Builder builder = new Request.Builder();

        if (headers != null) {
            headers.forEach(builder::addHeader);
        }

        Request request = builder.url(Url).post(body).build();
        return okHttpClient.newCall(request).execute();
    }

    public Response DELETE(String Url, JSONObject params, Map<String, String> headers) throws IOException {
        RequestBody body = FormBody.create(MediaType.parse("application/json; charset=utf-8"), params.toString());

        Request.Builder builder = new Request.Builder();

        if (headers != null) {
            headers.forEach(builder::addHeader);
        }

        Request request = builder.url(Url).delete(body).build();
        return okHttpClient.newCall(request).execute();
    }
}
